/*
 *  Copyright 2007-2008 by Matthias Buch-Kromann <mbk.isv@cbs.dk>.
 *  *
 *  This file is part of the Open-source Dependency Toolkit (OSDT),
 *  see http://code.google.com/p/open-source-dependency-toolkit.
 *  
 *  The OSDT is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Lesser General Public License as 
 *  published by the Free Software Foundation, either version 3 of 
 *  the License, or (at your option) any later version.
 * 
 *  The OSDT is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Lesser General Public License for more details.
 * 
 *  You should have received a copy of the GNU Lesser General Public 
 *  License along with the OSDT in the files COPYING and COPYING.LESSER.
 *  If not, see http://www.gnu.org/licenses.
 */

package org.osdtsystem.graphs.io;

import java.io.IOException;
import java.io.Writer;
import java.util.regex.Pattern;
import org.osdtsystem.exceptions.EdgeNotFoundException;
import org.osdtsystem.exceptions.LayerNotFoundException;
import org.osdtsystem.exceptions.LocationNotFoundException;
import org.osdtsystem.exceptions.NodeNotFoundException;
import org.osdtsystem.graphs.Graph;
import org.osdtsystem.graphs.views.View;
import org.osdtsystem.graphs.views.ViewID;
import org.osdtsystem.utils.Helper;
import org.osdtsystem.values.EdgeID;
import org.osdtsystem.values.Feature;
import org.osdtsystem.values.Layer;
import org.osdtsystem.values.NodeID;

/**
 *
 * @author Matthias Buch-Kromann <mbk.isv@cbs.dk>
 */
public class OsdtWriter implements GraphWriter {
    private Writer writer;
    private Graph graph;
    private GraphIDs ids;
    private boolean writeInternalFeatures = false;
    
    public OsdtWriter(Writer writer, Graph graph) throws IOException {
        this.writer = writer;
        this.graph = graph;
   
        // Number all views, layers, and nodes in the graph
        this.ids = new GraphIDs(graph);
        append(null);
    }    
    
    @Override
    public void close() throws IOException {
        writer.close();
    }

    @Override
    public String encoding() {
        return "osdt";
    }

    @Override
    public void flush() throws IOException {
        writer.flush();
    }

    @Override
    public void internalFeatures(boolean writeInternalFeatures) {
        this.writeInternalFeatures = writeInternalFeatures;
    }
    
    @Override
    public void append(String graphName) throws IOException {
        ids.compute();
        try {
            // Process views
            for (ViewID viewID : graph.views()) {
                View view = graph.view(viewID);
                if (view != null) {
                    String name = graph.viewName(view);
                    if (view == null)
                        throw new RuntimeException("Cannot find view name " + name +
                                " for view " + view + "with id " + viewID);
                    writer.write("VIEW");
                    Integer id = ids.id(view);
                    if (id == null)
                        id = ids.add(view);
                    writer.write(ids.id(view).toString());
                    writer.write(" \"");
                    writer.write(name);
                    writer.write("\" ");
                    writer.write(view.toOSDT(graph, ids));
                    writer.write("\n");
                }
            }
                
            // Process layers in sorted order
            for (Layer layer : Helper.sort(graph.layers())) {
                writeLayer(layer);
                writer.write("\n");
            }
        } catch (LayerNotFoundException ex) {
            // This should never happen!
            throw new RuntimeException("Unexpected inconsistency in graph state: cannot find layer name for layer returned by getLayers()", ex);
        }        
        
    }
    
    void writeLayer(final Layer layer)
            throws LayerNotFoundException, IOException {
        try {
            // Print layer line with parent layers and feature names
            writer.write("LAYER");
            writer.write(ids.id(layer).toString());
            writer.write(" ");
            writeString(graph.layerName(layer).toString());
            for (Feature f : Helper.sort(graph.features(layer, writeInternalFeatures))) {
                writer.write(" ");
                writeString(f.toString());
            }
            writer.write("\n");
            
            // Print nodes in layer in sort order
            for (NodeID node : Helper.sort(graph.nodes(layer)))
                writeNode(node);
            
            // Print edges in layer in sort order
            for (EdgeID edge : Helper.sort(graph.edges(layer)))
                writeEdge(edge);
        } catch (LocationNotFoundException ex) {
            // This should never happen!
            throw new RuntimeException("Unexpected inconsistency in graph state: cannot find node returned by getNodes()", ex);
        }
    } 
    
    void writeNode(final NodeID node) throws NodeNotFoundException, LocationNotFoundException, IOException {
        // Print node
        writer.write("  NODE");
        writer.write(ids.id(node).toString());

        // Print node features
        for (Feature f : Helper.sort(graph.features(node, writeInternalFeatures))) {
            Object v = graph.featureValue(node, f);
            if (v != null) {
                writer.write(" ");
                writeFeature(f, v);
            }
        }
        writer.write("\n");
    }
    
    void writeEdge(EdgeID edge) throws EdgeNotFoundException, IOException, LocationNotFoundException {
        writer.write("  EDGE ");
        writer.write(ids.id(graph.inNode(edge)).toString());
        writer.write("<");
        writer.write(ids.id(graph.outNode(edge)).toString());
        for (Feature f : Helper.sort(graph.features(edge, writeInternalFeatures))) {
            Object v = graph.featureValue(edge, f);
            if (v != null) {
                writer.write(" ");
                writeFeature(f, v);
            }
        }
        writer.write("\n");
    }
    
    void writeFeature(Feature f, Object v) throws IOException {
        writeString(f.toString());
        writer.write("=");
        writeString(v.toString());
    }
    
    void writeString(String s) throws IOException {
        for (int i = 0; i < osdtQuotePatterns.length ; ++i)
            s = osdtQuotePatterns[i].matcher(s).replaceAll(osdtQuoteReplacements[i]);
        String string = "\"" + s + "\"";
        writer.write(string);
    }
    
    // Static patterns for quoting and unquoting OSDT encoded graphs
    private static final Pattern[] osdtQuotePatterns 
            = {Pattern.compile("&"), Pattern.compile("\"")};
    private static final String[] osdtQuoteReplacements 
            = {"&amp;", "&quot;"};
    private static final Pattern[] osdtUnquotePatterns
            = {Pattern.compile("&quot;"), Pattern.compile("&amp;")};
    private static final String[] osdtUnquoteReplacements
            = {"\"", "&"};
}
